package your.rfgui.extgui.controller.tree;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.event.AjaxBehaviorEvent;
import org.apache.log4j.Logger;
import org.richfaces.component.UITree;
import org.richfaces.event.TreeSelectionChangeEvent;
import org.richfaces.model.SequenceRowKey;
import your.rfgui.extgui.controller.intf.TreeSelectionChangedListener;
import your.rfgui.extgui.data.intf.DataItem;

public abstract class BaseTreeController {

   private static final Logger log = Logger.getLogger(BaseTreeController.class);
   /** the top level nodes (typically only one root node with a label) */
   protected List<TreeViewNode> rootNodes;
   /** rich faces selection keys representing the currently selected tree node */
   private Collection<Object> selectionRowKeys = new ArrayList<Object>();
   /** currently selected tree node in GUI component */
   private TreeViewNode selectedViewNode;
   /** to be notified when the selected node in the tree changes */
   private TreeSelectionChangedListener selectionChangeListener;

   /**
    * Used by View. Returns top level view nodes for tree.
    * <rich:tree id="tree" value="#{bean.tree.rootNodes}" 
    */
   public List<TreeViewNode> getRootNodes() {
      return rootNodes;
   }

   /**
    * Used by View. Called on node selection in tree.
    * <rich:tree ... selection="#{bean.tree.selectionRowKeys} ... >
    * Rich Faces uses the returned collection also to change the selection
    */
   public Collection<Object> getSelectionRowKeys() {
      for (Object rowKey : selectionRowKeys) {
         Object[] selectionKeyArray = ((SequenceRowKey) rowKey).getSimpleKeys();
         log.info("getSelectionRowKeys: " + getSelectionKeyString(selectionKeyArray));
      }
      return selectionRowKeys;
   }

   /**
    * Used by View. Called for node selection in tree.
    * <rich:tree ... selection="#{bean.tree.selectionRowKeys} ... >
    */
   public void setSelectionRowKeys(Collection<Object> selectionRowKeys) {
      /** is not explicitely called by framework, obviously the element is added to the collection retrieved by getSelection() */
      this.selectionRowKeys = selectionRowKeys;
   }

   /**
    * --- Currently not used ---
    * Used by View. Called on selection change. Triggered by:
    * <rich:tree  ...  render="table">
    * <rich:treeSelectionChangeListener listener="#{bean.tree.processSelectionChange}" />
    */
   public void processSelectionChange(TreeSelectionChangeEvent selectionChangeEvent) {
      log.info("processSelectionChange");

      /** only one node may be selected */
      Collection<Object> currentSelectionRowKeys = selectionChangeEvent.getNewSelection();
      assert (currentSelectionRowKeys.size() == 1);
      Object currentSelectionRowKey = currentSelectionRowKeys.toArray()[0];

      UITree tree = (UITree) selectionChangeEvent.getSource();
      Object originalRowKey = tree.getRowKey();

      /** use rich faces selection keys provided by event */
      tree.setRowKey(currentSelectionRowKey);
      assert (tree.isRowAvailable());
      selectedViewNode = (TreeViewNode) tree.getRowData();
      tree.setRowKey(originalRowKey);

      log.info("processSelectionChange: selectedViewNode: " + selectedViewNode);
      if (selectionChangeListener != null) {
         selectionChangeListener.treeSelectionChanged(selectedViewNode);
      }
      /** if it is a leaf node: change selection to the parent */
      if (selectedViewNode.isLeaf()) {
         setSelectedViewNode(selectedViewNode.getParent());
      }
   }

   /**
    * Used by View. Called at selection change. Triggered by:
    * <rich:tree  ...  render="@none">
    * <a4j:ajax execute="@this" event="mouseup" listener="#{bean.tree.mouseupSelectionChange}" render="table"/>
    * Advantage: - is called with mouse-up and not with mouse-down event: table content changed with mouse-up
    *            - table does not change with drag-drop operation
    * Disadvantage: two HTTP-AJAX-calls (HTTP) are sent to the server: this one and
    *               getSelection() caused by '... selection="#{bean.tree.selection}"  ...'
    */
   public void mouseupSelectionChange(AjaxBehaviorEvent event) {
      log.info("mouseupSelectionChange: ");

      UITree tree = (UITree) event.getComponent();
      Object originalRowKey = tree.getRowKey();

      /** use rich faces selection keys provided to get/setSelectionRowKeys() */
      /** only one node may be selected */
      assert (selectionRowKeys.size() == 1);
      Object selectionRowKey = selectionRowKeys.toArray()[0];
      tree.setRowKey(selectionRowKey);
      assert (tree.isRowAvailable());
      selectedViewNode = (TreeViewNode) tree.getRowData();
      tree.setRowKey(originalRowKey);

      log.info("mouseupSelectionChange: selectedViewNode: " + selectedViewNode);

      /** notify listener if given */
      if (selectionChangeListener != null) {
         selectionChangeListener.treeSelectionChanged(selectedViewNode);
      }
      /** if it is a leaf node: change selection to the parent */
      if (selectedViewNode.isLeaf()) {
         setSelectedViewNode(selectedViewNode.getParent());
      }
   }

   /**
    * Used by application.
    * Get the currently selected view node.
    */
   public TreeViewNode getSelectedViewNode() {
      log.info("getSelectedViewNode: " + selectedViewNode);
      return selectedViewNode;
   }

   /**
    * Used by application.
    * Set or change the currently selected view node, change also selection in the GUI component.
    */
   public void setSelectedViewNode(TreeViewNode selectedViewNode) {
      log.info("setSelectedViewNode: " + selectedViewNode);

      this.selectedViewNode = selectedViewNode;
      List<Integer> selectionKeyList = new ArrayList<Integer>();
      evaluateSelectionKeys(selectionKeyList, selectedViewNode);

      Object[] selectionKeyArray = selectionKeyList.toArray();
      log.trace("setSelectedViewNode: " + getSelectionKeyString(selectionKeyArray));

      /** set rich faces selection keys */
      selectionRowKeys.clear();
      selectionRowKeys.add(new SequenceRowKey(selectionKeyArray));
   }

   /**
    * Used by application.
    * Get data item associated with the selected view node.
    */
   public DataItem getSelectedDataItem() {
      if (selectedViewNode != null) {
         log.info("getSelectedDataItem: " + selectedViewNode.getDataItem());
         return selectedViewNode.getDataItem();
      }
      log.info("getSelectedDataItem - null");
      return null;
   }

   /**
    * Used by application.
    * Set the listener notified when the selected node in the tree changes
    */
   public void setSelectionChangeListener(TreeSelectionChangedListener selectionChangeListener) {
      this.selectionChangeListener = selectionChangeListener;
   }

   /**
    * Helper method. Steps recursively the view node upwards to the root and evaluates the rich faces selection keys
    * used to set the selected tree node in the GUI component
    */
   private void evaluateSelectionKeys(List<Integer> keys, TreeViewNode viewNode) {

      TreeViewNode viewParent = viewNode.getParent();
      if (viewParent == null) {
         keys.add(0, new Integer(rootNodes.indexOf(viewNode)));
         return;
      }
      keys.add(0, new Integer(viewParent.getIndex(viewNode)));
      /** recursive call */
      evaluateSelectionKeys(keys, viewParent);
   }

   /**
    * Helper for stringifying the rich faces selection key array
    */
   private String getSelectionKeyString(Object[] selectionKeyArray) {
      StringBuilder keysStr = new StringBuilder(25);
      for (int i = 0; i < selectionKeyArray.length; i++) {
         if (i > 0) {
            keysStr.append("-");
         }
         keysStr.append(selectionKeyArray[i]);
      }
      return keysStr.toString();
   }
}
